using System;
using System.Threading;
using lucere.exception;

namespace lucere.io.concurrency
{
	/// <summary>
	/// An interprocess mutex lock.
	/// <p>Typical use might look like:<pre>
	///  Lock.With(Directory.MakeLock("my.lock"),1000,()=>DoWork())
	/// </pre>
	/// </p>
	/// <see cref="IDirectory.MakeLock(string)"></see>
	/// </summary>
	public abstract class Lock : ILock
	{
		/// <summary>
		/// How long <see cref="Obtain(int)"></see> waits, in milliseconds,in between attempts to acquire the lock.
		/// </summary>
		public static int LOCK_POLL_INTERVAL = 1000;

		/// <summary>
		/// Pass this value to <see cref="Obtain(int)"></see> to try
		/// forever to obtain the lock.
		/// </summary>
		public static int LOCK_OBTAIN_WAIT_FOREVER = -1;


		/// <summary>
		/// If a lock obtain called, this failureReason may be set
		/// with the "root cause" Exception as to why the lock was
		/// not obtained.
		/// </summary>
		protected Exception _failureReason;

		/// <summary>
		/// Attempts to obtain exclusive access and immediately return
		/// upon success or failure.
		/// </summary>
		/// <returns>true iff exclusive access is obtained</returns>
		public abstract bool Obtain();

		/// <summary>
		/// Attempts to obtain an exclusive lock within amount of
		/// time given. Polls once per <see cref="LOCK_POLL_INTERVAL"/>
		/// (currently 1000) milliseconds until lockWaitTimeout is
		/// passed.
		/// </summary>
		/// <param name="lockWaitTimeout">lockWaitTimeout length of time to wait in
		/// milliseconds or <see cref="LOCK_OBTAIN_WAIT_FOREVER"/>to retry forever
		/// </param>
		/// <returns>true if lock was obtained</returns>
		public virtual bool Obtain(int lockWaitTimeout)
		{
			_failureReason = null;
			bool locked = Obtain();
			if (lockWaitTimeout < 0 && lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER)
				throw new ArgumentException("lockWaitTimeout should be LOCK_OBTAIN_WAIT_FOREVER or a non-negative number (got " + lockWaitTimeout + ")");

			long maxSleepCount = lockWaitTimeout / LOCK_POLL_INTERVAL;
			long sleepCount = 0;
			while (!locked)
			{
				if (lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER && sleepCount++ >= maxSleepCount)
				{
					var reason = "Lock obtain timed out: " + ToString();
					if (_failureReason != null)
					{
						reason += ": " + _failureReason;
					}
					var e = _failureReason != null ? new LockObtainFailedException(reason, _failureReason) : new LockObtainFailedException(reason);
					throw e;
				}
				try
				{
					Thread.Sleep(LOCK_POLL_INTERVAL);
				}
				// ReSharper disable RedundantCatchClause
				catch (lucere.exception.ThreadInterruptedException)
				{
					//Note: Lucene throws specialized exception derived from RuntimeException to not pollute signature we simply rethrow
					throw;
				}
				// ReSharper restore RedundantCatchClause
				locked = Obtain();
			}
			return true;
		}

		/// <summary>
		/// Releases exclusive access.
		/// </summary>
		public abstract void Release();

		/// <summary>
		/// Returns true if the resource is currently locked.  Note that one must
		/// still call <see cref="ILock.Obtain()"/> before using the resource.
		/// </summary>
		/// <returns></returns>
		public abstract bool IsLocked();
		public static T With<T>(ILock @lock, int lockWaitTimeout, Func<T> doBody)
		{
			bool locked = false;
			try
			{
				locked = @lock.Obtain(lockWaitTimeout);
				return doBody();
			}
			finally
			{
				if (locked)
					@lock.Release();
			}
		}
	}
}